package at.ac.tuwien.lsds.model;

import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import org.apache.log4j.Logger;

import at.ac.tuwien.lsds.exception.MachineException;

public class VirtualMachineImpl extends MachineImpl implements VirtualMachine {
	private static final Logger log = Logger.getLogger(VirtualMachineImpl.class);

	// init values
	private Integer initHdd = 100;
	private Integer initRam = 50;
	private Integer initCpu = 150;

	private Queue<Application> apps;

	public VirtualMachineImpl() {
		apps = new LinkedList<Application>();

		this.hdd = initHdd;
		this.ram = initRam;
		this.cpu = initCpu;

		this.hddInUse = initHdd;
		this.ramInUse = initRam;
		this.cpuInUse = initCpu;
	}

	public VirtualMachineImpl(Integer hdd, Integer ram, Integer cpu) {
		apps = new LinkedList<Application>();
		

		this.hdd = hdd + initHdd;
		this.ram = ram + initRam;
		this.cpu = cpu + initCpu;

		this.hddInUse = initHdd;
		this.ramInUse = initRam;
		this.cpuInUse = initCpu;
	}

	@Override
	public synchronized void addApplication(Application app)
			throws MachineException {
		addApplication(app, true);
	}

	@Override
	public void addApplication(Application app, Boolean startImmediately)
			throws MachineException {
		int freeHdd = hdd - hddInUse;
		int freeRam = ram - ramInUse;
		int freeCpu = cpu - cpuInUse;

		if (freeHdd < app.getHdd() || freeRam < app.getRam()
				|| freeCpu < app.getCpu() || shutdown) {
			log.error("Not enough Ressources");
			throw new MachineException("Not enough Ressources");
		}

		apps.add(app);
		hddInUse += app.getHdd();
		ramInUse += app.getRam();
		cpuInUse += app.getCpu();

		
	}

	@Override
	public synchronized void removeAllApplication() {
		for (Application app : apps) {
			hddInUse -= app.getHdd();
			ramInUse -= app.getRam();
			cpuInUse -= app.getCpu();
		}

		apps.clear();
	}

	// TODO isFirst ueberdenken. was passiert wenn alle apps fertig waren...

	@Override
	public void setHdd(Integer hdd) throws MachineException {
		if (hdd < hddInUse) {
			throw new MachineException(
					"given hdd size is smaller than the current used size");
		}

		this.hdd = hdd;
	}

	@Override
	public void setRam(Integer ram) throws MachineException {
		if (ram < ramInUse) {
			throw new MachineException(
					"given ram size is smaller than the current used size");
		}

		this.ram = ram;
	}

	@Override
	public void setCpu(Integer cpu) throws MachineException {
		if (cpu < cpuInUse) {
			throw new MachineException(
					"given cpu size is smaller than the current used size");
		}

		this.cpu = cpu;
	}

	@Override
	public Integer getRunningAppCount() {
		return apps.size();
	}

	@Override
	public void shutdown() {
		shutdown = true;
	}

	@Override
	public void startup() {
		
	}

	/**
	 * 2013-05-18, Alex Faustmann
	 */
	@Override
	public String toString() {
		StringBuffer buffer = new StringBuffer();

		buffer.append("{ apps : [");
		for (Application app : apps) {
			buffer.append(app.toString());
		}
		buffer.append("]}");

		return buffer.toString();
	}

	private synchronized void setUsedResources(Integer hdd, Integer ram, Integer cpu) {
		hddInUse = hdd;
		ramInUse = ram;
		cpuInUse = cpu;

		setChanged();
		notifyObservers();
	}

	@Override
	public Collection<Application> getApplications() {
		return this.apps;
	}

	@Override
	public void increaseRunningTime() {

		ArrayList<Application> finishedApps = null;
		finishedApps = new ArrayList<Application>();

		for (Application app : apps) {

			if (app.getDuration() > 1) {
				// Application is going on
				app.setDuration(app.getDuration() - 1);
			} else {
				app.setDuration(0);
				// Application finished
				finishedApps.add(app);
			}

		}

		if (finishedApps != null && finishedApps.size() > 0){
			for (Application finishedApp : finishedApps) {
				apps.remove(finishedApp);
				
				setUsedResources(
					getHddInUse() - finishedApp.getHdd(),
					getRamInUse() - finishedApp.getRam(),
					getCpuInUse() - finishedApp.getCpu());
				
				log.info("Resources of VM updated.");
			}
		}
	}

}
